package org.emet.jee.core.dao.arch;

import java.io.Serializable;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;

import javax.management.relation.Role;

import org.apache.commons.dbcp.BasicDataSource;
import org.emet.jee.BusinessException;
import org.emet.jee.console.dao.sys.ArchDao;
import org.emet.jee.console.entity.security.Authority;
import org.emet.jee.console.entity.security.Department;
import org.emet.jee.console.entity.security.User;
import org.emet.jee.console.entity.sys.AppControl;
import org.emet.jee.console.entity.sys.Archive;
import org.emet.jee.console.entity.sys.DataDic;
import org.emet.jee.console.entity.sys.DataItem;
import org.emet.jee.core.EmetContext;
import org.emet.jee.core.dao.SaveOrUpdateListener;
import org.emet.jee.core.dao.hibernate.HibernateDao;
import org.emet.jee.core.utils.spring.SpringContextHolder;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
import org.springframework.orm.hibernate3.AbstractSessionFactoryBean;
import org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean;
import org.springframework.util.Assert;

/**
 * 
 * @author alex TODO I'd like to test the performance of this class
 * 
 * @param <T>
 * @param <PK>
 */
public class ArchDataSourceBaseDao<T, PK extends Serializable> extends HibernateDao<T, Serializable> implements
		ApplicationListener {

	private static Logger logger = LoggerFactory.getLogger(ArchDataSourceBaseDao.class);

	protected final static Map<Long, DataSourceModel> sesFac = new ConcurrentHashMap<Long, DataSourceModel>();

	public ArchDataSourceBaseDao() {
		super();
	}

	public static DataSourceModel getDataSourceModel(Long archId) {
		return sesFac.get(archId);
	}

	public static DataSourceModel getDefaultDataSourceModel() {
		Long archId = CurrentArchHolder.get();
		try {
			//			if (archId == null) {
			//				//For test
			//				archId = 1L;
			//				CurrentArchHolder.set(archId);
			//			}
			Assert.notNull(archId,
					"You are using distribute data source, however it's null/empty. To check if you are real need it.");

			if (sesFac.get(archId) == null) {
				synchronized (lockObject(archId)) {
					if (sesFac.get(archId) == null) {
						createSessionFactory(archId);
					}
				}
			}
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
			throw new BusinessException("Error in creating session factory. ", e);
		}

		return sesFac.get(archId);
	}

	@Override
	public Session getSession() {
		if (isSystemEntity(this.entityClass)) {
			System.err.println("using system session");
			return sessionFactory.getCurrentSession();
		}

		return getDefaultDataSourceModel().getSessionFactory().getCurrentSession();
	}

	private static String lockObject(Long archId) {
		return ("arch_" + archId).intern();
	}

	protected static SessionFactory createSessionFactory(Long archId) throws Exception {
		ArchDao archDao = SpringContextHolder.getBean("archDao");
		Archive archEntity = archDao.get(archId);
		AbstractSessionFactoryBean templateSessionFactory = createDefaultSessionFactoryBean();

		BasicDataSource ds = new BasicDataSource();
		ds.setDriverClassName(EmetContext.getProperty("jdbc.driver", null));
		ds.setUrl("jdbc:mysql://" + archEntity.getJdbcServer() + ":" + archEntity.getJdbcPort() + "/"
				+ archEntity.getJdbcSchema() + "?characterEncoding=utf8");
		ds.setUsername(archEntity.getJdbcUsername());
		ds.setPassword(archEntity.getJdbcPassword());

		templateSessionFactory.setDataSource(ds);
		templateSessionFactory.afterPropertiesSet();
		SessionFactory object = (SessionFactory) templateSessionFactory.getObject();
		Assert.notNull(object, "SessionFactory which we are creating is null.");

		final DataSourceModel dataSourceHolder = new DataSourceModel();
		dataSourceHolder.setDataSource(ds);
		dataSourceHolder.setSessionFactory(object);
		sesFac.put(archId, dataSourceHolder);

		logger.info("A new Arch was created: {}", archEntity.getName());
		return object;
	}

	protected static AbstractSessionFactoryBean createDefaultSessionFactoryBean() {
		AnnotationSessionFactoryBean factoryBean = new AnnotationSessionFactoryBean();
		factoryBean.setNamingStrategy(new org.hibernate.cfg.ImprovedNamingStrategy());
		Properties props = new Properties();
		props.put("hibernate.dialect", EmetContext.getProperty("hibernate.dialect", null));
		//		props.put("hibernate.show_sql", EmetContext.getProperty("hibernate.show_sql", null));
		//		props.put("hibernate.format_sql", EmetContext.getProperty("hibernate.format_sql", null));
		props.put("hibernate.cache.provider_class", "org.hibernate.cache.EhCacheProvider");
		props.put("hibernate.hbm2ddl.auto", "update");
		props.put("hibernate.cache.provider_configuration_file_resource_path",
				EmetContext.getProperty("hibernate.ehcache_config_file", null));
		props.put("hibernate.connection.release_mode", "after_transaction");
		factoryBean.setHibernateProperties(props);
		factoryBean.setPackagesToScan(EmetContext.getProperty("packagesToScan", "org.emet.entity.*").split(","));
		HashMap<String, List<Serializable>> map = new HashMap<String, List<Serializable>>();
		LinkedList<Serializable> linkedList = new LinkedList<Serializable>();
		linkedList.add(new SaveOrUpdateListener());
		linkedList.add(new org.hibernate.event.def.DefaultSaveOrUpdateEventListener());
		map.put("save-update", linkedList);
		factoryBean.setEventListeners(map);

		//AOP-based transaction support.
		//TODO Transasction is not supported now!
		//		HibernateTransactionManager hibernateTransactionManager = new HibernateTransactionManager();
		//		hibernateTransactionManager.setSessionFactory((SessionFactory) factoryBean.getObject());
		//
		//		new AnnotationTransactionAttributeSource();
		return factoryBean;
	}

	/**
	 * 判断该实体是否属于系统datasource，如果是则返回true
	 * 
	 * @param entityClass
	 * @return
	 */
	protected boolean isSystemEntity(Class<T> entityClass) {
		if (entityClass == Authority.class || entityClass == Department.class || entityClass == Role.class
				|| entityClass == User.class || entityClass == Archive.class || entityClass == AppControl.class
				|| entityClass == DataItem.class || entityClass == DataDic.class)
			return true;
		return false;
	}

	public SimpleJdbcTemplate getJdbcTemplate() {
		Long archId = CurrentArchHolder.get();
		if (archId == null) {
			getSession();
			archId = CurrentArchHolder.get();
		}
		DataSourceModel dataSourceHolder = sesFac.get(archId);
		return new SimpleJdbcTemplate(dataSourceHolder.getDataSource());
	}

	public void onApplicationEvent(ApplicationEvent event) {
		if (event.getSource() instanceof UpdateArchDataSourceEvent) {
			UpdateArchDataSourceEvent rmEvent = (UpdateArchDataSourceEvent) event.getSource();
			sesFac.remove(rmEvent.getArchId());
		}
	}

}
